/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.server.virtual.services;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.String;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import javax.jcr.NamespaceException;
import javax.jcr.NamespaceRegistry;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.Workspace;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.nodetype.NodeDefinition;
import javax.jcr.nodetype.NodeType;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.jcr.query.Row;
import javax.jcr.query.RowIterator;
import offset.nodes.Constants;
import offset.nodes.client.editor.model.PrintHandler;
import offset.nodes.client.editor.model.messages.GetQueryResults;
import offset.nodes.client.editor.model.messages.GetTemplatesForNodeTypePath;
import offset.nodes.client.editor.model.messages.GetVirtualInformation;
import offset.nodes.client.editor.model.messages.GetVirtualPageEditorInformation;
import offset.nodes.client.editor.model.messages.UploadData;
import offset.nodes.client.veditor.model.messages.UploadVirtualPage;
import offset.nodes.client.model.ServerRequest;
import offset.nodes.client.model.ServerResponse;
import offset.nodes.client.model.StringArrayConverter;
import offset.nodes.client.vdialog.model.GetPathForUUID;
import offset.nodes.client.vdialog.model.instance.GetTemplateInstance;
import offset.nodes.client.vdialog.model.instance.GetVirtualPageInformation;
import offset.nodes.client.vdialog.model.instance.NewTemplateInstance;
import offset.nodes.client.vdialog.model.instance.SetTemplateInstance;
import offset.nodes.client.vdialog.model.node.GetVirtualPageInstances;
import offset.nodes.client.vdialog.model.node.InstanceInfo;
import offset.nodes.client.vdialog.model.page.CreateNewVirtualPage;
import offset.nodes.client.dialog.remove.model.SetSchemas;
import offset.nodes.client.vdialog.model.template.GetTemplate;
import offset.nodes.client.vdialog.model.template.SetTemplate;
import offset.nodes.client.veditor.model.SecondaryQuery;
import offset.nodes.client.virtual.model.GetSchemas;
import offset.nodes.client.virtual.model.SimpleQuery;
import offset.nodes.client.virtual.model.jcr.NodeWriter;
import offset.nodes.client.virtual.model.jcr.QName;
import offset.nodes.client.virtual.model.jcr.SimpleNode;
import offset.nodes.server.controller.RepositoryPlugIn;
import offset.nodes.server.core.services.AccessControlServices;
import offset.nodes.server.core.services.TemplateInformationCreator;
import offset.nodes.server.core.services.XMLToJCRUpdater;
import offset.nodes.server.html.model.PageModel;
import offset.nodes.server.model.RepositoryModel;
import offset.nodes.server.model.ServerNamespaceRegistry;
import offset.nodes.server.search.model.SearchModel;
import offset.nodes.server.servlet.AbstractRepositoryService;
import offset.nodes.server.servlet.NodeObserver;
import offset.nodes.server.servlet.ResidualNameFilter;
import offset.nodes.server.servlet.Service;
import offset.nodes.server.servlet.ServiceContainer;
import offset.nodes.server.servlet.ServiceMapping;
import offset.nodes.server.virtual.model.StyleSheetCache;
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.core.nodetype.NodeTypeDef;
import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl;
import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
import org.apache.jackrabbit.core.nodetype.xml.NodeTypeReader;
import org.apache.jackrabbit.core.nodetype.xml.NodeTypeWriter;
import org.apache.jackrabbit.spi.Name;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author Walter L�tgenau
 */
public class VirtualServices extends AbstractRepositoryService implements ServiceContainer {

    static Logger logger = LoggerFactory.getLogger(VirtualServices.class);
    protected ServerNamespaceRegistry namespaces = new ServerNamespaceRegistry();

    @Override
    public ServiceMapping[] getServiceMappings() {
        return new ServiceMapping[]{
                    new ServiceMapping(new GetSchemas.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return getSchemas((GetSchemas.Request) request);
                }
            }, this),
                    new ServiceMapping(new SetTemplate.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return setTemplate((SetTemplate.Request) request);
                }
            }, this),
                    new ServiceMapping(new GetTemplate.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return getTemplate((GetTemplate.Request) request);
                }
            }, this),
                    new ServiceMapping(new SetSchemas.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return setSchemas((SetSchemas.Request) request);
                }
            }, this),
                    new ServiceMapping(new NewTemplateInstance.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return newTemplateInstance((NewTemplateInstance.Request) request);
                }
            }, this),
                    new ServiceMapping(new GetTemplateInstance.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return getTemplateInstance((GetTemplateInstance.Request) request);
                }
            }, this),
                    new ServiceMapping(new SetTemplateInstance.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return setTemplateInstance((SetTemplateInstance.Request) request);
                }
            }, this),
                    new ServiceMapping(new GetPathForUUID.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return getPathForUUID((GetPathForUUID.Request) request);
                }
            }, this),
                    new ServiceMapping(new GetVirtualPageInstances.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return getVirtualPageInstances((GetVirtualPageInstances.Request) request);
                }
            }, this),
                    new ServiceMapping(new CreateNewVirtualPage.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return createNewVirtualPage((CreateNewVirtualPage.Request) request);
                }
            }, this),
                    new ServiceMapping(new GetVirtualPageEditorInformation.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return getVirtualPageEditorInformation((GetVirtualPageEditorInformation.Request) request);
                }
            }, this),
                    new ServiceMapping(new UploadVirtualPage.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return uploadVirtualPage((UploadVirtualPage.Request) request);
                }
            }, this),
                    new ServiceMapping(new GetTemplatesForNodeTypePath.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return getTemplatesForNodeTypePath((GetTemplatesForNodeTypePath.Request) request);
                }
            }, this),
                    new ServiceMapping(new UploadData.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return uploadData((UploadData.Request) request);
                }
            }, this),
                    new ServiceMapping(new GetVirtualInformation.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return getVirtualInformation((GetVirtualInformation.Request) request);
                }
            }, this),
                    new ServiceMapping(new GetQueryResults.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return getQueryResults((GetQueryResults.Request) request);
                }
            }, this),
                    new ServiceMapping(new GetVirtualPageInformation.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return getVirtualPageInformation((GetVirtualPageInformation.Request) request);
                }
            }, this),};

    }

    /**
     * Return the information necessary for the virtual data editor.
     * 
     * @param request
     * @return
     * @throws NoSuchNodeTypeException
     * @throws NamespaceException
     * @throws RepositoryException
     * @throws IOException
     * @throws Exception
     */
    public GetVirtualInformation.Response getVirtualInformation(GetVirtualInformation.Request request) throws NoSuchNodeTypeException, NamespaceException, RepositoryException, IOException, Exception {
        GetVirtualInformation.Response response = new GetVirtualInformation.Response(ServerResponse.RESULT_SUCCESS);

        Session session = getSession(request);

        // retrieve transformation
        response.setTransformation(getString(getFile(getSession(request), request.getTransformationPath())));

        // retrieve instance information
        Node virtualPageInstance = getNode(getSession(request).getRootNode(), request.getInstancePath());
        Value[] secondaryQueriesInstance = new Value[0];

        if (virtualPageInstance.hasProperty(Constants.PROP_SECONDARY_QUERY))
            secondaryQueriesInstance = virtualPageInstance.getProperty(Constants.PROP_SECONDARY_QUERY).getValues();
        HashMap<String, String> secondaryQueries = createQueryMap(toStringArray(secondaryQueriesInstance));

        // retrieve schema information concerning multiple occurence of nodes
        ServerNamespaceRegistry namespaces = new ServerNamespaceRegistry();
        SessionImpl s = (SessionImpl) session;
        NodeTypeManagerImpl nmgr = s.getNodeTypeManager();
        Name[] names = nmgr.getNodeTypeRegistry().getRegisteredNodeTypes();
        NodeTypeDef[] nodeTypeDefs = new NodeTypeDef[names.length];
        HashMap<String, NodeTypeDef> nodeTypes = new HashMap<String, NodeTypeDef>();
        for (int i = 0; i < names.length; i++) {
            nodeTypeDefs[i] = nmgr.getNodeTypeRegistry().getNodeTypeDef(names[i]);
            String prefixName = namespaces.toPrefixName(names[i]);
            nodeTypes.put(prefixName, nodeTypeDefs[i]);
        }

        TemplateInformationCreator templateInformationCreator = new TemplateInformationCreator(namespaces, nodeTypes, response.getTransformation(), secondaryQueries);
        response.setNodeTypeInformation(templateInformationCreator.createTemplateInformation(request.getNodeTypeName()));

        return response;
    }

    class WorkflowForwardCreator implements NodeObserver {

        String forwardPath = null;
        String workflowUUID = null;
        String workflowDocumentType = null;
        String newState = null;

        public WorkflowForwardCreator(Node root, Node templateInstance) throws RepositoryException {
            /* If the templateInstance is part of a workflow, i.e. located below a workflow node,
             * determine the workflow uuid and the state marked for creating new workflow document nodes */
            Node node = templateInstance;
            while (!node.isSame(root) && workflowUUID == null) {
                if (node.isNodeType(Constants.TYPE_WORKFLOW)) {
                    if (node.hasProperty(Constants.PROP_DOCUMENT_TYPE))
                        workflowDocumentType = node.getProperty(Constants.PROP_DOCUMENT_TYPE).getString();
                    workflowUUID = node.getUUID();
                    break;
                }

                node = node.getParent();
            }

            /* if the template instance is not part of a workflow, check, if it is referenced by workflow */
            if (workflowUUID == null) {
                PropertyIterator pi = templateInstance.getReferences();
                while (pi.hasNext()) {
                    Property reference = pi.nextProperty();
                    if (reference.getParent().isNodeType(Constants.TYPE_TEMPLATE_EDITOR)) {
                        node = reference.getParent().getParent().getParent();
                        if (node.hasProperty(Constants.PROP_DOCUMENT_TYPE))
                            workflowDocumentType = node.getProperty(Constants.PROP_DOCUMENT_TYPE).getString();
                        workflowUUID = node.getUUID();
                        break;
                    }
                }
            }

            // still not found. Give up
            if (workflowUUID == null)
                return;

            NodeIterator ni = node.getNodes();
            while (ni.hasNext()) {
                Node state = ni.nextNode();
                if (!state.isNodeType(Constants.TYPE_STATE))
                    continue;

                if (state.hasProperty(Constants.PROP_NEW_STATE) && state.getProperty(Constants.PROP_NEW_STATE).getBoolean()) {
                    newState = state.getName();
                    break;
                }
            }

        }

        public void nodeCreated(Node node) {
            try {
                // if this is a new workflow document node, set workflow and current state
                if (node.isNodeType(Constants.TYPE_WORKFLOW_DOCUMENT)) {
                    if (!node.hasProperty(Constants.PROP_WORKFLOW) && workflowUUID != null
                            && workflowDocumentType != null
                            && node.getPrimaryNodeType().getName().equals(workflowDocumentType)) {
                        node.setProperty(Constants.PROP_WORKFLOW, workflowUUID);
                        if (!node.hasProperty(Constants.PROP_CURRENT_STATE) && newState != null)
                            node.setProperty(Constants.PROP_CURRENT_STATE, newState);
                    }

                    forwardPath = "?ref=" + node.getUUID() + "&contentType=workflow";
                }
            } catch (RepositoryException ex) {
                java.util.logging.Logger.getLogger(AccessControlServices.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        public String getForwardPath() {
            return forwardPath;
        }
    }

    public UploadData.Response uploadData(UploadData.Request request) throws RepositoryException {
        UploadData.Response response = new UploadData.Response(ServerResponse.RESULT_SUCCESS);

        Session session = getSession(request);
        Node instance = getNode(session.getRootNode(), request.getInstancePath());

        String nodeType = instance.getProperty(Constants.PROP_NODETYPE).getString();

        NodeTypeDef nodeTypePrimaryQuery = getNodeTypeDefinition(session, instance.getProperty(Constants.PROP_NODETYPE).getString());
        String userPropertyPrimaryQuery = getUserProperty(session, nodeTypePrimaryQuery);
        String commentPropertyPrimaryQuery = getCommentProperty(nodeTypePrimaryQuery);

        String path = null;
        if (instance.hasProperty(Constants.PROP_PRIMARY_QUERY)) {
            SimpleQuery query = new SimpleQuery(instance.getProperty(Constants.PROP_PRIMARY_QUERY).getString());
            path = query.getPath();
        }
        // if the new node shall not be created in the default folder, use the requested folder
        if (request.getPath() != null && request.getPath().length() > 0)
            path = request.getPath();

        Node primaryRoot = null;
        if (request.getUuid() != null) {
            RepositoryModel model = new RepositoryModel(session);
            primaryRoot = model.getNodeByUuid(request.getUuid()).getParent();
        } else if (path != null)
            primaryRoot = getNode(session.getRootNode(), path);

        SecondaryQueryInfo[] secondaryQueries = getSecondaryQueryInfos(session, instance);

        WorkflowForwardCreator forwardCreator = new WorkflowForwardCreator(session.getRootNode(), instance);
        XMLToJCRUpdater updater = new XMLToJCRUpdater(session, primaryRoot);
        updater.setMode(XMLToJCRUpdater.Mode.update);
        SecondaryQueryFilter secondaryQueryFilter = new SecondaryQueryFilter(session, updater,
                request.getUserName(), request.getComment(),
                userPropertyPrimaryQuery, commentPropertyPrimaryQuery,
                secondaryQueries);
        updater.addFilter(secondaryQueryFilter);
        updater.addFilter(new ResidualNameFilter());
        updater.addNodeObserver(forwardCreator);
        for (SecondaryQueryInfo info : secondaryQueries) {
            if (info.getReferenceDirection() == SimpleQuery.REFERENCE_FROM_THIS)
                updater.addSettableProperty(nodeType, info.getReferenceProperty());
        }

        updater.update(request.getData());

        session.save();

        if (forwardCreator.getForwardPath() != null)
            response.setForward(getRequest().getContextPath() + "/" + Constants.SERVLETMAPPING_REPOSITORY + forwardCreator.getForwardPath());
        return response;
    }

    protected GetSchemas.Response getSchemas(GetSchemas.Request request) throws RepositoryException {
        GetSchemas.Response response = new GetSchemas.Response(ServerResponse.RESULT_SUCCESS);
        Repository repository = RepositoryPlugIn.getRepository();

        Session session = getSession(request);
        ByteArrayOutputStream out = new ByteArrayOutputStream();

        try {
            SessionImpl s = (SessionImpl) session;
            NodeTypeManagerImpl nmgr = s.getNodeTypeManager();
            Name[] names = nmgr.getNodeTypeRegistry().getRegisteredNodeTypes();
            NodeTypeDef[] nodeTypes = new NodeTypeDef[names.length];
            for (int i = 0; i < names.length; i++) {
                nodeTypes[i] = nmgr.getNodeTypeRegistry().getNodeTypeDef(names[i]);
            }
            NodeTypeWriter.write(out, nodeTypes, namespaces);
        } catch (IOException eio) {
            throw new RepositoryException(eio);
        }

        response.setSchemaXml(out.toString());

        return response;
    }

    public GetTemplate.Response getTemplate(GetTemplate.Request request) throws Exception {
        GetTemplate.Response response = new GetTemplate.Response(ServerResponse.RESULT_SUCCESS);

        Session session = getSession(request);
        Node template = getNode(session.getRootNode(), request.getPath());

        if (template.getPrimaryNodeType().getName().equals(Constants.TYPE_TEMPLATE)) {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            NodeWriter writer = new NodeWriter(template, new PrintHandler(out));
            writer.write();

            response.setTemplate(new String(out.toByteArray()));
            return response;
        } else
            response.setResult(ServerResponse.RESULT_FAILURE);

        return response;
    }

    public SetTemplate.Response setTemplate(SetTemplate.Request request) throws RepositoryException {
        SetTemplate.Response response = new SetTemplate.Response(ServerResponse.RESULT_SUCCESS);

        Session session = getSession(request);
        Node node = getNode(session.getRootNode(), request.getPath());

        XMLToJCRUpdater updater = new XMLToJCRUpdater(session, node);
        updater.update(request.getTemplate());

        if (updater.getUpdatedNodes().size() == 0)
            return new SetTemplate.Response(ServerResponse.RESULT_FAILURE);

        Node updatedNode = updater.getUpdatedNodes().get(0);
        Node representation = updatedNode.getNode(Constants.CHILD_ROOT + Constants.PATH_SEPARATOR + Constants.CHILD_REPRESENTATION);
        invalidateStyleSheetCache(representation);

        session.save();

        return response;
    }

    public SetSchemas.Response setSchemas(SetSchemas.Request request) throws RepositoryException {
        SetSchemas.Response response = new SetSchemas.Response(ServerResponse.RESULT_SUCCESS);
        Repository repository = RepositoryPlugIn.getRepository();

        Session session = getSession(request);
        try {
            // Get the NodeTypeManager from the Workspace.
            // Note that it must be cast from the generic JCR NodeTypeManager to the
            // Jackrabbit-specific implementation.
            SessionImpl s = (SessionImpl) session;
            NodeTypeManagerImpl ntmgr = s.getNodeTypeManager();
            NamespaceRegistry nsr = session.getWorkspace().getNamespaceRegistry();

            // build nodes namespace registry
            updateNamespaces(nsr, namespaces);

            // Acquire the NodeTypeRegistry
            NodeTypeRegistry ntreg = ntmgr.getNodeTypeRegistry();

            // add new node types
            if (request.getAdded() != null) {
                NodeTypeDef[] definitions = NodeTypeReader.read(new ByteArrayInputStream(request.getAdded().getBytes()));
                definitions = sortDependencies(definitions);
                for (int i = 0; i < definitions.length; i++) {
                    ntreg.registerNodeType(definitions[i]);
                }
            }

            // update modified node types
            if (request.getModified() != null) {
                NodeTypeDef[] definitions = NodeTypeReader.read(new ByteArrayInputStream(request.getModified().getBytes()));
                definitions = sortDependencies(definitions);
                for (int i = 0; i < definitions.length; i++) {
                    ntreg.reregisterNodeType(definitions[i]);
                }
            }

            // remove obsolete node types
            if (request.getRemoved() != null) {
                NodeTypeDef[] definitions = NodeTypeReader.read(new ByteArrayInputStream(request.getRemoved().getBytes()));
                for (int i = 0; i < definitions.length; i++) {
                    ntreg.unregisterNodeType(definitions[i].getName());
                }
            }
        } catch (Exception e) {
            throw new RepositoryException(e);
        }

        return response;
    }

    public NewTemplateInstance.Response newTemplateInstance(NewTemplateInstance.Request request) throws Exception {
        NewTemplateInstance.Response response = new NewTemplateInstance.Response(ServerResponse.RESULT_SUCCESS);

        Session session = getSession(request);
        Node templateInstance = getNode(session.getRootNode(), request.getTemplateInstancePath());

        Node template = getNode(session.getRootNode(), request.getTemplatePath());
        Node templateRoot = template.getNode(Constants.CHILD_ROOT);
        SimpleNode newInstance = new SimpleNode(templateInstance.getName());
        newInstance.setProperty(Constants.JCR_PRIMARY_TYPE, Constants.TYPE_TEMPLATE_INSTANCE);
        newInstance.setProperty(Constants.PROP_CONTENT_TYPE, Constants.CONTENT_TYPE_VIRTUAL);
        newInstance.setProperty(Constants.PROP_DEFAULT_CONTENT_TYPE, Constants.CONTENT_TYPE_VIRTUAL);

        addTemplateInstanceFromTemplateNode(newInstance, templateRoot);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        NodeWriter writer = new NodeWriter(newInstance, new PrintHandler(out));
        writer.write();

        response.setConfiguration(new String(out.toByteArray()));

        return response;
    }

    public GetTemplateInstance.Response getTemplateInstance(GetTemplateInstance.Request request) throws Exception {
        GetTemplateInstance.Response response = new GetTemplateInstance.Response(ServerResponse.RESULT_SUCCESS);

        Session session = getSession(request);
        Node templateInstance = getNode(session.getRootNode(), request.getTemplateInstancePath());

        if (templateInstance.getPrimaryNodeType().getName().equals(Constants.TYPE_TEMPLATE_INSTANCE)) {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            NodeWriter writer = new NodeWriter(templateInstance, new PrintHandler(out));
            writer.write();

            response.setConfiguration(new String(out.toByteArray()));
            return response;
        } else
            response.setResult(ServerResponse.RESULT_FAILURE);

        return response;
    }

    public SetTemplateInstance.Response setTemplateInstance(SetTemplateInstance.Request request) throws Exception {
        SetTemplateInstance.Response response = new SetTemplateInstance.Response(ServerResponse.RESULT_SUCCESS);

        Session session = getSession(request);
        Node templateInstance = getNode(session.getRootNode(), request.getTemplateInstancePath());

        XMLToJCRUpdater updater = new XMLToJCRUpdater(session, templateInstance);
        updater.update(request.getConfiguration());

        session.save();

        return response;
    }

    /**
     * Return informtion about a virtual template page.
     *
     * @param request
     * @return
     * @throws RepositoryException
     */
    public GetVirtualPageInformation.Response getVirtualPageInformation(GetVirtualPageInformation.Request request) throws RepositoryException {
        GetVirtualPageInformation.Response response = new GetVirtualPageInformation.Response(ServerResponse.RESULT_SUCCESS);

        Session session = getSession(request);
        Node node = session.getNodeByUUID(request.getUuid());

        response.setPath(node.getPath());
        if (node.hasProperty(Constants.PROP_SECONDARY_QUERY)) {
            Value[] secondaryQueries = node.getProperty(Constants.PROP_SECONDARY_QUERY).getValues();
            response.setSecondaryQueries(new String[secondaryQueries.length]);
            for (int i = 0; i < secondaryQueries.length; i++) {
                response.getSecondaryQueries()[i] = secondaryQueries[i].getString();
            }
        }

        return response;
    }

    /**
     * Return the path for a given UUID
     *
     * @param request
     * @return
     * @throws RepositoryException
     */
    public GetPathForUUID.Response getPathForUUID(GetPathForUUID.Request request) throws RepositoryException {
        GetPathForUUID.Response response = new GetPathForUUID.Response(ServerResponse.RESULT_SUCCESS);

        Session session = getSession(request);
        Node node = session.getNodeByUUID(request.getUuid());
        response.setPath(node.getPath());

        return response;
    }

    /**
     * Return information about the available virtual page instances.
     *
     * @param request
     * @return
     * @throws RepositoryException
     */
    public GetVirtualPageInstances.Response getVirtualPageInstances(GetVirtualPageInstances.Request request) throws RepositoryException {
        GetVirtualPageInstances.Response response = new GetVirtualPageInstances.Response(ServerResponse.RESULT_SUCCESS);

        Workspace workspace = getSession(request).getWorkspace();
        QueryManager manager = workspace.getQueryManager();
        Query query = manager.createQuery(SearchModel.REPOSITORY_ROOT + "//element(*, " + Constants.TYPE_VIRTUAL_PAGE_INSTANCE + ")", Query.XPATH);
        NodeIterator result = query.execute().getNodes();

        LinkedList<InstanceInfo> instanceInformations = new LinkedList<InstanceInfo>();
        while (result.hasNext()) {
            Node instance = result.nextNode();

            if (!instance.hasProperty(Constants.PROP_NODETYPE))
                continue;

            InstanceInfo info = new InstanceInfo(instance.getProperty(Constants.PROP_NODETYPE).getString(), instance.getPath(), instance.getUUID());
            instanceInformations.add(info);
        }

        response.setInstances(instanceInformations.toArray(new InstanceInfo[instanceInformations.size()]));

        return response;
    }
    public static final String VIRTUAL_PAGE_SAMPLE = "/offset/nodes/server/virtual/model/emptyVirtualPage.html";
    public static final String VIRTUAL_PAGE_TRANSFORMATION = "/offset/nodes/server/virtual/model/emptyVirtualPage.xsl";
    public static final String NAME_NODE_TYPE = "_NODE_TYPE_NAME_";
    public static final String NAME_TEMPLATE = "_TEMPLATE_NAME_";

    /**
     * Create a new, empty virtual page. This includes a nodes:templateNode and the nodes:virtualPageInstance.
     * 
     * @param request
     * @return
     */
    public CreateNewVirtualPage.Response createNewVirtualPage(CreateNewVirtualPage.Request request) throws RepositoryException, IOException {
        CreateNewVirtualPage.Response response = new CreateNewVirtualPage.Response(ServerResponse.RESULT_SUCCESS);

        Session session = getSession(request);
        Node root = getNode(session.getRootNode(), request.getPath());

        // create root dir
        Node parent = root.addNode(request.getName());

        // create template
        Node template = parent.addNode(Constants.CHILD_TEMPLATE, Constants.TYPE_TEMPLATE_NODE);
        Node virtualPage = template.addNode(Constants.CHILD_REPRESENTATION, Constants.TYPE_VIRTUAL_PAGE);
        virtualPage.setProperty(Constants.PROP_NODETYPE, request.getNodeType());

        RepositoryModel model = new RepositoryModel(getSession(request));

        String sample = readString(this.getClass().getResourceAsStream(VIRTUAL_PAGE_SAMPLE));
        sample = sample.replaceAll(NAME_NODE_TYPE, request.getNodeType());
        model.storeBlob(virtualPage, Constants.CHILD_SAMPLE, null, sample.getBytes());

        String transformation = readString(this.getClass().getResourceAsStream(VIRTUAL_PAGE_TRANSFORMATION));
        transformation = transformation.replaceAll(NAME_NODE_TYPE, request.getNodeType());

        String templateName = request.getNodeType().replaceAll(":", "_");
        transformation = transformation.replaceAll(NAME_TEMPLATE, templateName);

        model.storeBlob(virtualPage, Constants.CHILD_TRANSFORMATION, null, transformation.getBytes());
        model.storeBlob(virtualPage, Constants.CHILD_EDITORTRANSFORMATION, null, transformation.getBytes());

        // create instance
        template.refresh(true);
        Node instance = parent.addNode(Constants.CHILD_INSTANCE, Constants.TYPE_VIRTUAL_PAGE_INSTANCE);
        instance.setProperty(Constants.PROP_NODETYPE, request.getNodeType());
        instance.setProperty(Constants.PROP_TEMPLATE_NODE_REFERENCE, template.getUUID());

        session.save();

        response.setInstancePath(instance.getPath());

        return response;
    }

    /**
     * Return information used by the virtual page editor.
     * 
     * @param request
     * @return
     */
    public GetVirtualPageEditorInformation.Response getVirtualPageEditorInformation(GetVirtualPageEditorInformation.Request request) throws RepositoryException, MalformedURLException {
        GetVirtualPageEditorInformation.Response response = new GetVirtualPageEditorInformation.Response(ServerResponse.RESULT_SUCCESS);

        RepositoryModel model = new RepositoryModel(getSession(request));
        Node virtualPageInstance = getNode(getSession(request).getRootNode(), request.getInstancePath());
        Value[] secondaryQueriesInstance = new Value[0];
        if (virtualPageInstance.hasProperty(Constants.PROP_SECONDARY_QUERY))
            secondaryQueriesInstance = virtualPageInstance.getProperty(Constants.PROP_SECONDARY_QUERY).getValues();
        Value[] selectQueriesInstance = new Value[0];
        if (virtualPageInstance.hasProperty(Constants.PROP_SELECT_QUERY))
            selectQueriesInstance = virtualPageInstance.getProperty(Constants.PROP_SELECT_QUERY).getValues();

        Node templateNode = getSession(request).getNodeByUUID(virtualPageInstance.getProperty(Constants.PROP_TEMPLATE_NODE_REFERENCE).getString());
        response.setTemplateNodePath(templateNode.getPath());
        Value[] secondaryQueriesTemplate = new Value[0];
        if (templateNode.hasProperty(Constants.PROP_SECONDARY_QUERY))
            secondaryQueriesTemplate = templateNode.getProperty(Constants.PROP_SECONDARY_QUERY).getValues();
        Value[] selectQueriesTemplate = new Value[0];
        if (templateNode.hasProperty(Constants.PROP_SELECT_QUERY))
            selectQueriesTemplate = templateNode.getProperty(Constants.PROP_SELECT_QUERY).getValues();

        Node virtualPage = templateNode.getNode(Constants.CHILD_REPRESENTATION);

        Node sample = virtualPage.getNode(Constants.CHILD_SAMPLE);
        String styleSheetSearchPath = null;
        if (virtualPage.hasNode(Constants.CHILD_CSS))
            styleSheetSearchPath = virtualPage.getNode(Constants.CHILD_CSS).getPath();
        else
            styleSheetSearchPath = sample.getPath();
        response.setSampleHtml(extractBodyAndAddHTMLHeader(model.getBlob(sample).getString(), styleSheetSearchPath));

        response.setSecondaryQueries(adjustInstanceQueries(toStringArray(secondaryQueriesTemplate), toStringArray(secondaryQueriesInstance)));
        response.setSelectQueries(adjustInstanceQueries(toStringArray(selectQueriesTemplate), toStringArray(selectQueriesInstance)));

        return response;
    }

    /**
     * Update a virtual page. That is, both an instance and the associated template.
     *
     * @param request
     * @return
     */
    public UploadVirtualPage.Response uploadVirtualPage(UploadVirtualPage.Request request) throws RepositoryException {
        UploadVirtualPage.Response response = new UploadVirtualPage.Response(ServerResponse.RESULT_SUCCESS);

        RepositoryModel model = new RepositoryModel(getSession(request));

        // instance
        Node virtualPageInstance = getNode(getSession(request).getRootNode(), request.getInstancePath());
        virtualPageInstance.setProperty(Constants.PROP_SECONDARY_QUERY, request.getInstanceInfo().getSecondaryQueries());
        virtualPageInstance.setProperty(Constants.PROP_SELECT_QUERY, request.getInstanceInfo().getSelectQueries());

        virtualPageInstance.save();

        // template
        if (request.getTemplateInfo() != null) {
            Node templateNode = getSession(request).getNodeByUUID(virtualPageInstance.getProperty(Constants.PROP_TEMPLATE_NODE_REFERENCE).getString());
            templateNode.setProperty(Constants.PROP_SECONDARY_QUERY, request.getTemplateInfo().getSecondaryQueries());
            templateNode.setProperty(Constants.PROP_SELECT_QUERY, request.getTemplateInfo().getSelectQueries());

            Node virtualPage = templateNode.getNode(Constants.CHILD_REPRESENTATION);

            Node sample = virtualPage.getNode(Constants.CHILD_SAMPLE);
            model.updateBlob(sample, request.getTemplateInfo().getSampleHtml().getBytes());

            Node transformation = virtualPage.getNode(Constants.CHILD_TRANSFORMATION);
            model.updateBlob(transformation, request.getTemplateInfo().getStyleSheet().getBytes());

            Node editorTransformation = virtualPage.getNode(Constants.CHILD_EDITORTRANSFORMATION);
            model.updateBlob(editorTransformation, request.getTemplateInfo().getEditorStyleSheet().getBytes());

            if (request.getTemplateInfo().getCascadingStyleSheet() != null)
                if (virtualPage.hasNode(Constants.CHILD_CSS)) {
                    Node styleSheet = virtualPage.getNode(Constants.CHILD_CSS);
                    model.updateBlob(styleSheet, request.getTemplateInfo().getCascadingStyleSheet().getBytes());
                } else {
                    Node styleSheet = model.storeBlob(virtualPage, Constants.CHILD_CSS, Constants.MIMETYPE_CSS, request.getTemplateInfo().getCascadingStyleSheet().getBytes());
                    styleSheet.setProperty(Constants.PROP_CONTENT_TYPE, Constants.CONTENT_TYPE_BINARY);
                }

            templateNode.save();
            invalidateStyleSheetCache(virtualPage);
        }

        return response;
    }

    /**
     * Add the existing templateInformation for the nodeType to the list.
     * Ignore the current template, which shall be extended.
     * 
     * @param session
     * @param templates
     * @param nodeType
     * @param ignoreTemplatePath The template, that is currently used. Ignore this one.
     * @throws RepositoryException
     */
    protected void addTemplates(Session session, List<GetTemplatesForNodeTypePath.TemplateInformation> templates, String nodeType, String ignoreThisTemplate) throws RepositoryException {
        Workspace workspace = session.getWorkspace();
        QueryManager manager = workspace.getQueryManager();
        Query query = manager.createQuery(SearchModel.REPOSITORY_ROOT + "//element(*, " + Constants.TYPE_VIRTUAL_PAGE + ")[@" + Constants.PROP_NODETYPE + "='" + nodeType + "']", Query.XPATH);
        NodeIterator result = query.execute().getNodes();

        RepositoryModel model = new RepositoryModel(session);

        if (result.getSize() > 0)
            while (result.hasNext()) {
                Node virtualPage = result.nextNode();
                Node templateNode = virtualPage.getParent();
                if (templateNode.getPath().equals(ignoreThisTemplate))
                    continue;

                GetTemplatesForNodeTypePath.TemplateInformation template = new GetTemplatesForNodeTypePath.TemplateInformation();
                template.setPath(templateNode.getPath());
                template.setUuid(templateNode.getUUID());

                Node sample = virtualPage.getNode(Constants.CHILD_SAMPLE);
                template.setSampleHtml(model.getBlob(sample).getString());
                template.setNodeType(nodeType);

                templates.add(template);
            }
    }

    /**
     * Return the existing templates from the node type path.
     *
     * This is used by the virtual page template editor to reuse existing templates.
     * 
     * @param request
     * @return
     * @throws RepositoryException
     */
    public GetTemplatesForNodeTypePath.Response getTemplatesForNodeTypePath(GetTemplatesForNodeTypePath.Request request) throws RepositoryException {
        GetTemplatesForNodeTypePath.Response response = new GetTemplatesForNodeTypePath.Response(ServerResponse.RESULT_SUCCESS);

        Session session = getSession(request);

        String nodeTypeName = getNodeTypeNameFromPath(session, request.getNodeTypePath());

        RepositoryModel model = new RepositoryModel(session);
        Node instance = model.getNode(request.getInstancePath());
        Node template = instance.getProperty(Constants.PROP_TEMPLATE_NODE_REFERENCE).getNode();

        addTemplates(session, response.getTemplates(), nodeTypeName, template.getPath());

        NodeTypeDef nodeTypeDefinition = getNodeTypeDefinition(session, nodeTypeName);
        for (Name nodeType : nodeTypeDefinition.getSupertypes()) {
            addTemplates(session, response.getTemplates(), namespaces.toPrefixName(nodeType), template.getPath());
        }

        return response;
    }

    /**
     * Execute a virtual page named query and return the results.
     *
     * @param request
     * @return
     * @throws RepositoryException
     */
    public GetQueryResults.Response getQueryResults(GetQueryResults.Request request) throws RepositoryException {
        GetQueryResults.Response response = new GetQueryResults.Response(ServerResponse.RESULT_SUCCESS);

        Session session = getSession(request);

        String path = request.getInstancePath();
        if (path.startsWith(Constants.PATH_SEPARATOR))
            path = path.substring(1);
        Node instance = session.getRootNode().getNode(path);
        String queryString = null;
        for (Value value : instance.getProperty(Constants.PROP_SELECT_QUERY).getValues()) {
            String[] queryItem = value.getString().split("=");
            if (queryItem.length != 2)
                continue;

            if (request.getQueryName().equals(queryItem[0])) {
                queryString = queryItem[1];
                break;
            }
        }

        LinkedList<String> queryResults = new LinkedList<String>();
        if (queryString != null) {
            // expand query path
            queryString = expandQueryPath(session, queryString, request.getReferencePath(), request.getReferenceUuid());

            // process NAME property
            String modifiedQueryString = queryString.replaceAll(QName.NAME_PROPERTY.getLocalName(), Constants.JCR_PATH);
            boolean nameProperty = !queryString.equals(modifiedQueryString);

            // run query
            Workspace workspace = session.getWorkspace();
            QueryManager manager = workspace.getQueryManager();
            Query query = manager.createQuery(SearchModel.REPOSITORY_ROOT + modifiedQueryString, Query.XPATH);
            RowIterator result = query.execute().getRows();
            while (result.hasNext()) {
                Row row = result.nextRow();
                String value = row.getValues()[0].getString();

                if (nameProperty) {
                    int start = value.lastIndexOf(Constants.PATH_SEPARATOR);
                    if (start >= 0)
                        value = value.substring(start + 1);
                }

                queryResults.add(value);
            }
        }

        response.setResults(queryResults.toArray(new String[queryResults.size()]));

        return response;
    }

    /**
     * Extract the parameter in a query including ANCESTOR_NAME(parameter)
     * or ANCESTOR_TYPE(parameter)
     *
     * @param query
     * @param function
     * @return
     */
    protected String extractParameter(String query, String function) {
        int start = query.indexOf("(", query.indexOf(function));
        if (start < 0)
            return query;
        int end = query.indexOf(")", start);
        if (end < 0)
            return query;

        return query.substring(start + 1, end);
    }

    /**
     * Replace the parameter in a query including a function with
     * a parameter
     * 
     * @param query
     * @param function
     * @param replacement
     * @return
     */
    protected String replaceParameter(String query, String function, String replacement) {
        int start = query.indexOf(function);
        int end = query.indexOf(")", start);
        if (end < 0)
            return query;

        return query.substring(0, start) + replacement + query.substring(end + 1);
    }

    /**
     * Expand the query. The query may include path patterns like ANCESTOR_NAME(<name>) or ANCESTOR_TYPE(<type>).
     * These refer to a node within the path to the reference node, that has either the given name or the given node type, respectively.
     * Substitute the patterns by the path of the target node and return the query.
     *
     * @param session
     * @param query
     * @param referencePath
     * @param referenceUuid
     * @return
     * @throws RepositoryException
     */
    protected String expandQueryPath(Session session, String query, String referencePath, String referenceUuid) throws RepositoryException {
        boolean hasAncestorName = query.indexOf(Constants.QUERY_ROOT_ANCESTOR_NAME) >= 0;
        boolean hasAncestorType = query.indexOf(Constants.QUERY_ROOT_ANCESTOR_TYPE) >= 0;

        if (!hasAncestorName && !hasAncestorType)
            return query;

        Node reference = null;
        if (referencePath != null && referencePath.length() > 0) {
            if (referencePath.startsWith(Constants.PATH_SEPARATOR))
                referencePath = referencePath.substring(1);
            reference = session.getRootNode().getNode(referencePath);
        } else if (referenceUuid != null && referenceUuid.length() > 0)
            reference = session.getNodeByUUID(referenceUuid);

        if (reference != null)
            if (hasAncestorName) {
                String name = extractParameter(query, Constants.QUERY_ROOT_ANCESTOR_NAME);
                while (!reference.getName().equals(name) && reference.getDepth() > 0) {
                    reference = reference.getParent();
                }
                if (reference.getDepth() == 0)
                    return query;
                return replaceParameter(query, Constants.QUERY_ROOT_ANCESTOR_NAME, reference.getPath());
            } else if (hasAncestorType) {
                String type = extractParameter(query, Constants.QUERY_ROOT_ANCESTOR_TYPE);
                while (!reference.getPrimaryNodeType().getName().equals(type) && reference.getDepth() > 0) {
                    reference = reference.getParent();
                }
                if (reference.getDepth() == 0)
                    return query;
                return replaceParameter(query, Constants.QUERY_ROOT_ANCESTOR_TYPE, reference.getPath());
            }

        return query;
    }

    protected HashMap<String, String> createQueryMap(String[] queries) {
        HashMap<String, String> result = new HashMap<String, String>();

        if (queries != null)
            for (int i = 0; i < queries.length; i++) {
                if (queries[i] == null || queries[i].length() == 0)
                    continue;
                int start = queries[i].indexOf("=");
                if (start < 0) {
                    result.put(queries[i], "");
                    continue;
                }

                result.put(queries[i].substring(0, start), queries[i].substring(start+1));
            }

        return result;
    }

    /**
     * Balance the template and the instance queries.
     *
     * Remove all instance queries, that are not present in the template.
     * Add empty instance queries for values only present in the template, but not in the
     * instance queries.
     * 
     * @param template
     * @param instance
     * @return
     */
    protected String[] adjustInstanceQueries(String[] template, String[] instance) {
        HashMap<String, String> templateQueries = createQueryMap(template);
        HashMap<String, String> instanceQueries = createQueryMap(instance);

        for (String key : templateQueries.keySet()) {
            if (!instanceQueries.containsKey(key))
                instanceQueries.put(key, templateQueries.get(key));
        }

        List<String> result = new LinkedList<String>();
        for (String key : instanceQueries.keySet()) {
            if (templateQueries.containsKey(key))
                result.add(key + "=" + instanceQueries.get(key));
        }

        return result.toArray(new String[result.size()]);
    }

    /**
     * Return the node type name encoded by the path.
     *
     * The path will start with a node type name, followed by the child node names, separated by '/' from each other.
     * 
     * @param session
     * @param path
     * @return
     * @throws NoSuchNodeTypeException
     */
    protected String getNodeTypeNameFromPath(Session session, String path) throws NoSuchNodeTypeException {
        String[] components = path.split(Constants.PATH_SEPARATOR);
        if (components.length == 0)
            return null;

        if (components.length == 1)
            return components[0];

        SessionImpl s = (SessionImpl) session;
        NodeTypeManagerImpl nmgr = s.getNodeTypeManager();
        NodeType nodeType = nmgr.getNodeType(components[0]);

        for (int i = 1; i < components.length; i++) {
            boolean found = false;

            for (NodeDefinition child : nodeType.getChildNodeDefinitions()) {
                if (components[i].equals(child.getName())) {
                    nodeType = nmgr.getNodeType(child.getDefaultPrimaryType().getName());
                    found = true;
                    break;
                }
            }

            if (!found)
                return null;
        }

        return nodeType.getName();
    }

    /**
     * Convert a value array consisting of strings to a
     * string array.
     * 
     * @param values
     * @return
     * @throws RepositoryException
     */
    protected String[] toStringArray(Value[] values) throws RepositoryException {
        String[] result = new String[values.length];

        for (int i = 0; i < values.length; i++) {
            result[i] = values[i].getString();
        }

        return result;

    }

    /**
     * Read the whole content of the input stream into a string.
     * 
     * @param in
     * @return
     * @throws IOException
     */
    protected String readString(InputStream in) throws IOException {
        byte[] buf = new byte[in.available()];
        in.read(buf);

        return new String(buf);
    }

    protected void updateNamespaces(NamespaceRegistry registry, ServerNamespaceRegistry namespaces) {
        try {
            String[] uris = namespaces.getURIs();
            for (int i = 0; i < uris.length; i++) {
                String prefix = null;
                try {
                    prefix = registry.getPrefix(uris[i]);
                } catch (Exception e) {
                    try {
                        registry.unregisterNamespace(namespaces.getPrefix(uris[i]));
                    } catch (Exception e1) {
                    }
                    registry.registerNamespace(namespaces.getPrefix(uris[i]), uris[i]);
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    protected NodeTypeDef[] sortDependencies(NodeTypeDef[] input) {
        ArrayList in = new ArrayList();
        for (int i = 0; i < input.length; i++) {
            in.add(input[i]);
        }
        ArrayList out = new ArrayList();

        int added = 0;
        do {
            added = 0;
            Iterator it = in.iterator();
            while (it.hasNext()) {
                NodeTypeDef definition = (NodeTypeDef) it.next();
                if (!hasDependency(definition, in, out)) {
                    if (!containsDefinition(out, definition.getName()))
                        out.add(definition);
                    it.remove();
                    added++;
                }

            }
        } while (in.size() > 0 && added > 0);

        return (NodeTypeDef[]) out.toArray(new NodeTypeDef[out.size()]);
    }

    /**
     * Return a node with a relative path to the root. Create path nodes, if necessory
     *
     * @param root
     * @param path
     * @return
     * @throws javax.jcr.RepositoryException
     */
    protected Node getNode(Node root, String path) throws RepositoryException {
        RepositoryModel repositoryModel = new RepositoryModel(root.getSession());
        return repositoryModel.getNode(root, path);
    }

    protected Node addTemplateInstanceFromTemplateNode(Node templateInstanceParent, Node templateNode) throws Exception {
        Node templateInstance = createTemplateInstanceFromTemplateNode(templateInstanceParent, templateNode);

        NodeIterator ni = templateNode.getNodes();
        while (ni.hasNext()) {
            Node node = ni.nextNode();
            if (node.getPrimaryNodeType().getName().equals(Constants.TYPE_TEMPLATE_NODE))
                addTemplateInstanceFromTemplateNode(templateInstance, node);
        }

        return templateInstance;
    }

    protected boolean hasDependency(NodeTypeDef definition, List in, List out) {
        boolean result = false;

        Collection dependencies = definition.getDependencies();
        Iterator it = dependencies.iterator();
        while (it.hasNext()) {
            Name name = (Name) it.next();
            // if the output does not already contain the dependency and the dependency is to one of the new elements
            if (containsDefinition(in, name) && !containsDefinition(out, name))
                return true;
        }

        return result;
    }

    protected boolean containsDefinition(List definitions, Name name) {
        Iterator it = definitions.iterator();
        while (it.hasNext()) {
            NodeTypeDef definition = (NodeTypeDef) it.next();
            if (definition.getName().equals(name))
                return true;
        }

        return false;
    }

    protected Node createTemplateInstanceFromTemplateNode(Node parent, Node templateNode) throws Exception {
        Node node = parent.addNode(Constants.CHILD_TEMPLATE_INSTANCE);
        if (templateNode.isNodeType(Constants.JCR_REFERENCEABLE))
            node.setProperty(Constants.PROP_TEMPLATE_NODE_REFERENCE, templateNode.getProperty(Constants.JCR_UUID).getString());

        Node representation = templateNode.getNode(Constants.CHILD_REPRESENTATION);
        if (representation.hasProperty(Constants.PROP_NAME))
            node.setProperty(Constants.PROP_NAME, representation.getProperty(Constants.PROP_NAME).getString());
        else if (representation.hasProperty(Constants.PROP_NAME_PROPERTY))
            node.setProperty(Constants.PROP_NAME_PROPERTY, representation.getProperty(Constants.PROP_NAME_PROPERTY).getString());

        if (representation.hasProperty(Constants.PROP_NODETYPE))
            node.setProperty(Constants.PROP_NODETYPE, representation.getProperty(Constants.PROP_NODETYPE).getString());

        if (templateNode.hasProperty(Constants.PROP_PRIMARY_QUERY))
            node.setProperty(Constants.PROP_PRIMARY_QUERY, templateNode.getProperty(Constants.PROP_PRIMARY_QUERY).getString());
        if (templateNode.hasProperty(Constants.JCR_UUID))
            node.setProperty(Constants.PROP_TEMPLATE_NODE_REFERENCE, templateNode.getProperty(Constants.JCR_UUID).getString());

        if (representation.getPrimaryNodeType().getName().equals(Constants.TYPE_VIRTUAL_FOLDER))
            node.setProperty(Constants.JCR_PRIMARY_TYPE, Constants.TYPE_VIRTUAL_FOLDER_INSTANCE);
        else if (representation.getPrimaryNodeType().getName().equals(Constants.TYPE_VIRTUAL_PAGE)) {
            node.setProperty(Constants.JCR_PRIMARY_TYPE, Constants.TYPE_VIRTUAL_PAGE_INSTANCE);
            if (templateNode.hasProperty(Constants.PROP_SECONDARY_QUERY)) {
                Value[] values = templateNode.getProperty(Constants.PROP_SECONDARY_QUERY).getValues();
                StringArrayConverter sa = new StringArrayConverter(values);
                for (int i = 0; i < values.length; i++) {
                    node.setProperty(Constants.PROP_SECONDARY_QUERY, sa.toString());
                }
            }
        }

        return node;
    }

    protected void invalidateStyleSheetCache(Node representation) throws RepositoryException {
        if (representation.hasNode(Constants.CHILD_TRANSFORMATION))
            StyleSheetCache.getInstance().invalidateTemplate(representation.getPath() + Constants.PATH_SEPARATOR + Constants.CHILD_TRANSFORMATION);

        if (representation.hasNode(Constants.CHILD_EDITORTRANSFORMATION))
            StyleSheetCache.getInstance().invalidateTemplate(representation.getPath() + Constants.PATH_SEPARATOR + Constants.CHILD_EDITORTRANSFORMATION);

        NodeIterator i = representation.getNodes(Constants.CHILD_REPRESENTATION);
        while (i.hasNext()) {
            invalidateStyleSheetCache(i.nextNode());
        }
    }

    protected String getUserProperty(Session session, NodeTypeDef nodeTypeDefinition) throws RepositoryException {
        String result = null;

        Name[] superTypes = nodeTypeDefinition.getSupertypes();

        for (int i = 0; i < superTypes.length; i++) {
            String prefixName = namespaces.toPrefixName(superTypes[i]);
            if (prefixName.equals(Constants.NODES_VERSIONABLE)) {
                result = Constants.PROP_CREATOR;
                break;
            }
            // is a supertype of a supertype?
            result = getUserProperty(session, getNodeTypeDefinition(session, prefixName));
            if (result != null)
                break;
        }

        return result;
    }

    protected String getCommentProperty(NodeTypeDef nodeTypeDefinition) throws RepositoryException {
        String result = null;

        Name[] superTypes = nodeTypeDefinition.getSupertypes();

        for (int i = 0; i < superTypes.length; i++) {
            if (namespaces.toPrefixName(superTypes[i]).equals(Constants.NODES_VERSIONABLE)) {
                result = Constants.PROP_COMMENT;
                break;
            }
        }

        return result;
    }

    protected SecondaryQueryInfo[] getSecondaryQueryInfos(Session session, Node instance) throws RepositoryException {
        Node root = session.getRootNode();

        if (!instance.hasProperty(Constants.PROP_SECONDARY_QUERY))
            return new SecondaryQueryInfo[0];

        Value[] secondaryQueryValues = instance.getProperty(Constants.PROP_SECONDARY_QUERY).getValues();
        SecondaryQueryInfo[] result = new SecondaryQueryInfo[(int) secondaryQueryValues.length];
        for (int i = 0; i < result.length; i++) {
            String secondary = secondaryQueryValues[i].getString();

            SecondaryQuery secondaryQuery = new SecondaryQuery(secondary);

            SimpleQuery query = new SimpleQuery(secondaryQuery.getQuery());
            String nodeTypeName = query.getNodeType();

            NodeTypeDef nodeTypeDefinition = getNodeTypeDefinition(session, nodeTypeName);

            result[i] = new SecondaryQueryInfo();
            try {
                if (query.isValidPath())
                    result[i].setDataRoot(getNode(root, query.getPath()));
            } catch (RepositoryException e) {
                // we can simply leaves this null, as no data will be retrieved by this query
                logger.error("No access", e);
            }

            result[i].setNodeType(nodeTypeName);
            result[i].setReferenceProperty(query.getReferenceProperty());
            result[i].setReferenceDirection(query.getReferenceDirection());
            result[i].setUserProperty(getUserProperty(session, nodeTypeDefinition));
            result[i].setCommentProperty(getCommentProperty(nodeTypeDefinition));
        }

        return result;
    }

    protected NodeTypeDef getNodeTypeDefinition(Session session, String name) throws RepositoryException {
        SessionImpl s = (SessionImpl) session;
        NodeTypeManagerImpl nmgr = s.getNodeTypeManager();
        return nmgr.getNodeTypeRegistry().getNodeTypeDef(prefixNameToQName(name));
    }

    protected Name prefixNameToQName(String prefixName) {
        return namespaces.toName(prefixName);
    }

    protected InputStream getFile(Session session, String path) throws RepositoryException {
        while (path.startsWith("/")) {
            path = path.substring(1);
        }

        Node root = session.getRootNode();
        Node pageNode = root.getNode(path + "/" + "jcr:content");
        if (pageNode == null)
            return null;

        InputStream input = pageNode.getProperty("jcr:data").getValue().getStream();

        return input;
    }

    protected String getName(String path) {
        int start = path.lastIndexOf(Constants.PATH_SEPARATOR);
        if (start < 0)
            return path;

        return path.substring(start + 1);
    }

    protected String getString(InputStream in) throws IOException {
        byte[] buf = new byte[in.available()];
        in.read(buf);
        in.close();

        return new String(buf);
    }

    /**
     * Extract the body element from the HTML passed and add a header with all CSS links
     * of the current path plus a single (template specific) CSS provided by the caller.
     *
     * @param html The HTML
     * @param styleSheetSearchPath The path used to search for style sheets
     * @return the page text
     * @throws RepositoryException
     */
    protected String extractBodyAndAddHTMLHeader(String html, String styleSheetSearchPath) throws RepositoryException, MalformedURLException {
        String bodyTag = "body";
        int startBody = html.indexOf("<" + bodyTag + ">");
        int endBody = html.indexOf("</" + bodyTag + ">");

        if (startBody < 0 || endBody < 0)
            return html;

        String body = html.substring(startBody, endBody + ("</" + bodyTag + ">").length());
        StringBuilder page = new StringBuilder("<html><head>");

        page.append(createLink(getRepositoryServletUrl() + Constants.PATH_SEPARATOR + Constants.DIR_CSS + Constants.PATH_SEPARATOR + Constants.CSS_MAIN));
        PageModel model = new PageModel(RepositoryModel.getRepositorySession(getRequest()));
        if (styleSheetSearchPath != null) {
            Iterator<String> css = model.getCSSInPath(styleSheetSearchPath).iterator();
            while (css.hasNext()) {
                String cssPath = css.next();
                page.append(createLink(getRepositoryServletUrl() + cssPath));
            }
        }

        page.append("</head>");
        page.append(body);
        page.append("</html>");

        return page.toString();
    }

    /**
     * Return the URL of the repository servlet
     *
     * @return the URL as a String
     * @throws MalformedURLException
     */
    protected String getRepositoryServletUrl() throws MalformedURLException {
        URL dialogUrl = new URL(getRequest().getRequestURL().toString());
        URL repositoryUrl = new URL(dialogUrl.getProtocol(),
                dialogUrl.getHost(),
                dialogUrl.getPort(),
                getRequest().getContextPath() + Constants.PATH_SEPARATOR + Constants.SERVLETMAPPING_REPOSITORY);
        return repositoryUrl.toString();
    }

    /**
     * Create a link HTML element from the path provided by the caller
     *
     * @param linkPath The link path
     * @return the link element
     */
    protected String createLink(String linkPath) {
        return "<link rel=\"stylesheet\" type=\"text/css\" href=\"" + linkPath + "\"/>";
    }


}
